<!DOCTYPE html>
<html lang="en" dir="auto">

<head><script src="/WangJV-Blog-Pages/livereload.js?mindelay=10&amp;v=2&amp;port=1313&amp;path=WangJV-Blog-Pages/livereload" data-no-instant defer></script><meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="robots" content="noindex, nofollow">
<title>EWA Splatting | WangJV Blog</title>
<meta name="keywords" content="体渲染, 铺摊算法, EWA, 计算机图形学">
<meta name="description" content="1. 介绍
体渲染是科学和工程应用中可视化获取和模拟数据集的一项重要技术。理想的体渲染算法在三维空间中重建一个连续函数，将这个三维函数转换到相机空间中，然后沿着视线计算中间中连续函数的不透明度积分。1989年，Westover引入了用于交互式体渲染的铺摊算法，该算法近似实现了这一过程。铺摊算法将体数据视为一组吸收和发射光线的粒子。该积分预先在每个粒子上计算，形成足迹函数。每个足迹（或铺摊）在图像平面上传播其贡献。这些贡献从后到前组合成最终图像。另一方面，激光测距和基于图像的扫描技术产生了一些迄今为止最复杂和最引人注目的图形模型，导致了大量的表面点样本。常用的方法是从点数据生成三角网格，并使用网格简化技术进行渲染。
相比之下，最近的工作重点是直接渲染没有连通性的点样本。大多数这些方法基于类似于体渲染中的铺摊方法。在本文中，我们提出了一种高质量铺摊的框架。我们的推导与Heckbert的椭圆加权平均（EWA）纹理滤波器的方法相似，因此我们称我们的算法为EWA铺摊。EWA铺摊的主要特点是它集成了一个椭圆高斯重建核和一个低通滤波器，因此在输出图像中防止了混叠伪影，同时避免了过度模糊。此外，我们使用相同的框架来推导体数据和表面数据的铺摊原语。EWA体铺摊使用任意椭圆高斯重建核，并有效支持透视投影。我们的方法基于一种计算足迹函数的新方法，该方法依赖于将体数据转换到所谓的射线空间。这种转换等同于透视投影。通过在每个体素上使用其局部仿射近似，我们推导出EWA足迹在屏幕空间中的解析表达式。
EWA体铺摊原语可以很容易地集成到常规体铺摊算法中。由于其灵活性，它可以用于渲染直线型、曲线型或非结构化的体数据集。足迹的光栅化使用前向差分进行，只需一个一维足迹表即可用于所有重建核和任何视角。EWA表面铺摊等同于三角形渲染管线的EWA纹理滤波器的屏幕空间形式。因此，它为点采样表面提供了高质量的各向异性纹理滤波。
我们将展示，通过将高斯体重建核减少到表面重建核，可以从EWA体铺摊推导出EWA表面铺摊。因此，EWA铺摊是体数据和表面数据的通用渲染原语。例如，我们可以通过沿体积梯度压平三维高斯核来执行高质量的等值面渲染。本文组织如下：第二部分讨论以前的工作。第三部分回顾信号处理理论中的基本结果，这些结果是分析混叠所需的。我们还介绍了理想重采样滤波器的一般概念，该滤波器通过结合重建核和低通滤波器防止在渲染过程中出现混叠。接下来，在第四部分中，我们描述如何将体渲染建模为一个重采样过程，从而得出理想的体重采样滤波器。同样，我们在第五部分中推导出渲染点采样表面的理想重采样滤波器。在第六部分中，我们介绍了EWA重采样滤波器，该滤波器使用椭圆高斯作为重建核和低通滤波器。我们给出了EWA体重采样滤波器和EWA表面重采样滤波器的明确公式。此外，我们展示了如何通过压平体重建核，将表面重采样滤波器作为体重采样滤波器的特例来推导。最后，第七和第八部分讨论了我们的实现和结果，第九部分总结了本文。
2. 相关工作
在体渲染的相关文章中，关于散点技术的最初工作是由Westover提出的。基本的体散点算法在从后向前合成散点时由于可见性确定不准确而受到影响，这导致了明显的伪影，例如颜色渗漏。后来，Westover使用轴对齐的片缓冲区解决了这个问题。然而，这种技术在动画中容易出现令人不快的跳跃伪影。最近，Mueller和Crawfis提出了将片缓冲区与图像平面平行对齐，而不是与体数据的轴平行对齐。此外，他们分别散射每个重建核的几个切片。这种技术类似于基于切片的体渲染，不会出现跳跃伪影。Mueller和Yagel结合了散点和光线投射技术，以加速透视投影的渲染。Laur和Hanrahan描述了一种分层散点算法，使渲染过程中的逐步细化成为可能。此外，Lippert和Gross引入了一种直接使用体数据的小波表示的散点算法。
如果三维核不是径向对称的，例如对于直线网格、曲线网格或不规则网格，则需要特别注意。此外，对于三维空间中的任意位置，所有核的贡献必须相加为一。否则，图像中会出现斑点等伪影。对于直线网格，Westover提出了使用椭圆足迹，然后将其扭曲回圆形足迹的方案。为了渲染曲线网格，Mao使用随机泊松重采样生成一组新点，这些点的核是球形或椭圆形的。他计算椭圆足迹的方法与Westover十分相似。如第6.2节所述，我们的技术可以用于直线网格、曲线网格和不规则网格，以高效准确地投影和光栅化椭圆散点核。Westover的原始框架没有处理透视投影引起的采样率变化。在体数据中，发散射线的采样率低于体网格采样率的区域可能会出现混叠伪影。体散点中的混叠问题首先由Swan等和Mueller等解决。他们通过依距离伸展足迹，使其充当低通滤波器。相反，EWA散点在一个统一的框架中建模了重建和带限纹理函数的功能。
Levoy和Whitted的开创性报告引入了将表面表示为点集并将其作为渲染原语的概念。由于几何复杂性的不断增加，他们的想法最近引起了更多的关注。QSplat是一个点渲染系统，旨在交互式渲染由现代扫描设备生成的大型数据集。其他研究人员展示了基于点的方法在渲染几何复杂对象方面的效率。在某些系统中，点基表示暂时存储在渲染管线中以加速渲染。我们在工作中系统地解决了在点采样对象上表示纹理函数并避免渲染时混叠的问题。表面散点技术可以替代以前方法中使用的启发式方法，并提供更优质的纹理质量。我们沿着Heckbert的开创性工作发展了EWA散点，他引入了EWA滤波以避免表面纹理的混叠。我们最近扩展了他的框架，以表示和渲染不规则点采样表面上的纹理函数和体散点。第6.4节将展示EWA体散点和表面散点之间的联系。
3. 理想采样
3.1. 采样和混叠
混叠是计算机图形学中的一个基本问题。尽管从概念上讲，计算机图形学通常处理图形模型的连续表示，但在实际操作中，计算机生成的图像是由离散的样本数组表示的。图像合成涉及连续和离散表示之间的转换，这可能会导致混叠伪影，例如如图1所示的摩尔纹和锯齿边缘，或动画中的闪烁。
为了研究混叠问题，将图像、表面纹理或体数据解释为多维信号是有用的。在接下来的讨论中，我们将重点讨论一维信号，并在第4和第5节中返回多维信号。当一个连续信号被转换为离散信号时，它将在一个离散网格上进行评估或采样。为了分析采样的效果并理解信号的连续表示和离散表示之间的关系，我们回顾一些信号处理理论中的定义和结果。滤波器是一种将信号作为输入并生成修改后的信号或响应作为输出的过程。最容易理解的一类滤波器是线性空间不变滤波器。线性空间不变滤波器L由其脉冲响应 $h(x)$​ 唯一表征，即其对脉冲输入的输出。因此，线性空间不变滤波器对任何输入信号f(x)的响应由f(x)和h(x)的卷积给出。

$$
 \mathcal{L}\{f(x)\}=\int_{-\infty}^{&#43;\infty}f(t)h(x-t)dt=(f\otimes h)(x).
$$
一个基本的的分析滤波器的方法是计算滤波器的特征值和特征函数。线性时不变滤波器的特征函数是一个复指数函数，特征值由冲击响应的傅立叶变换给出，这被称为频率响应。信号 $f(x)$ 的傅立叶变换被成为该信号的频谱，定义为 $F(\omega)$。其中 $\omega$​ 是频率。对于连续时间信号而言，我们往往使用傅立叶变换和频域来分析。在采样一个连续信号时，我们将被采样的信号与一个脉冲序列相乘，但是采样的过程会导致在频域上产生混叠。为了重建原始连续信号，我们需要滤除所有重复的信号。如果混叠的信号没有重叠，可以通过一个门滤波器来实现。

3.2. 抗混叠
根据以上讨论，我们得出结论，有两种方法可以减少混叠问题：我们可以通过更高频率对连续信号进行采样，或者在采样之前消除高于奈奎斯特限的频率，这称为预滤波。由于大多数感兴趣的信号不是带限的，以更高频率进行采样虽然可以缓解，但不能完全避免混叠。此外，增加采样频率会导致大多数算法的内存和计算要求增加。另一方面，预滤波是通过在采样之前对信号应用低通滤波器来实现的，因此它是更为理论上合理的抗混叠方法。使用截止频率为 $\omega_s=2$ 的理想低通滤波器，滤波后的信号将被带限制在采样网格的奈奎斯特频率内，因此可以精确重建。实际上，预滤波是作为空间域中的卷积来实现的，因此，为了提高效率，需要使用支持范围较小的预滤波器。然而，滤波器在空间域和频域的宽度是反比关系，因此在采样过程中不可避免地会出现一些混叠
3.3. 渲染和重采样
在我们的框架中，图形模型表示为描述对象属性（如体积不透明度或表面纹理）的多维函数的不规则间隔样本集。我们通过计算加权和来重建连续的属性函数。

$$
f_c(u)= \sum_{k\in \mathbb{IN}}\omega_kr_k(u)
$$
其中 $r_k$ 被称为以采样点 $u_k$ 为中心重建核；$\omega_k$ 是采样的值，即在 $u_k$ 点的颜色。我们定义原始的世界为 $f(u_k)$​。我们将属性方程渲染出来的过程表述为一个重采样过程，包含三个步骤。


将 $f_c(u)$ 投影到屏幕空间上，产生连续的屏幕空间信号 $g_c(u)$：

$$
g_c(\mathbf{x})=\{\mathcal{P}(f_c)\}(\mathbf{x}),
$$其中 $x$ 是二维相机坐标系的坐标，并且投影过程被定义为一个投影算子 $\mathcal{P}$，我们可以用重建核的形式来重写上面的投影过程：

$$
g_c(\mathbf{x})=\left\{\mathcal{P}{\left(\sum_{k\in\mathrm{IN}}w_kr_k\right)}\right\}(\mathbf{x})=\sum_{k\in\mathrm{IN}}w_kp_k(\mathbf{x}),
$$
我们将投影过程的重建核简写为$p_k = \mathcal{P}_k$。

将屏幕空间上的信号的幅值限制到屏幕能表达的范围内：

$$
g_c^{\prime}(\mathbf{x})=g_c(\mathbf{x})\otimes h(\mathbf{x})=\int_{\mathbf{IR}^2}g_c(\boldsymbol{\eta})h(\mathbf{x}-\boldsymbol{\eta})d\boldsymbol{\eta}.
$$
让屏幕空间上的连续信号和一个采样序列相乘使其离散化。

$$
g(x)=g&#39;(x)i(x)
$$将上述关系式按相反顺序展开，即可得出投影连续输出函数的明确表达式：
$$
\begin{aligned}
g_c&#39;(\mathbf{x})& =\int_{\text{IR}^2}\left\{\mathcal{P}\left(\sum_{k\in\text{IN}}w_kr_k\right)\right\}(\boldsymbol{\eta})h(\mathbf{x}-\boldsymbol{\eta})d\boldsymbol{\eta}  \\
&=\sum_{k\in\mathbf{IN}}w_k\int_{\mathbf{IR}^2}p_k(\eta)h(\mathbf{x}-\eta)d\boldsymbol{\eta} \\
&=\sum_{k\in\mathbf{IN}}w_k\rho_k(\mathbf{x}), \\
&\mathrm{where}\quad\rho_k(\mathbf{x})=(p_k\otimes h)(\mathbf{x}).
\end{aligned}
$$我们将投影和滤波器的重建核 $\rho_k$ 为重采样核，在屏幕空间内表达为一个卷积。">
<meta name="author" content="WangJV">
<link rel="canonical" href="http://localhost:1313/WangJV-Blog-Pages/2024/06/ewa-splatting/">
<link crossorigin="anonymous" href="http://localhost:1313/WangJV-Blog-Pages/assets/css/stylesheet.8fe10233a706bc87f2e08b3cf97b8bd4c0a80f10675a143675d59212121037c0.css" integrity="sha256-j&#43;ECM6cGvIfy4Is8&#43;XuL1MCoDxBnWhQ2ddWSEhIQN8A=" rel="preload stylesheet" as="style">
<link rel="icon" href="http://localhost:1313/WangJV-Blog-Pages/favicon.ico">
<link rel="icon" type="image/png" sizes="16x16" href="http://localhost:1313/WangJV-Blog-Pages/favicon-16x16.png">
<link rel="icon" type="image/png" sizes="32x32" href="http://localhost:1313/WangJV-Blog-Pages/favicon-32x32.png">
<link rel="apple-touch-icon" href="http://localhost:1313/WangJV-Blog-Pages/apple-touch-icon.png">
<link rel="mask-icon" href="http://localhost:1313/WangJV-Blog-Pages/safari-pinned-tab.svg">
<meta name="theme-color" content="#2e2e33">
<meta name="msapplication-TileColor" content="#2e2e33">
<link rel="alternate" hreflang="en" href="http://localhost:1313/WangJV-Blog-Pages/2024/06/ewa-splatting/">
<noscript>
    <style>
        #theme-toggle,
        .top-link {
            display: none;
        }

    </style>
    <style>
        @media (prefers-color-scheme: dark) {
            :root {
                --theme: rgb(29, 30, 32);
                --entry: rgb(46, 46, 51);
                --primary: rgb(218, 218, 219);
                --secondary: rgb(155, 156, 157);
                --tertiary: rgb(65, 66, 68);
                --content: rgb(196, 196, 197);
                --code-block-bg: rgb(46, 46, 51);
                --code-bg: rgb(55, 56, 62);
                --border: rgb(51, 51, 51);
            }

            .list {
                background: var(--theme);
            }

            .list:not(.dark)::-webkit-scrollbar-track {
                background: 0 0;
            }

            .list:not(.dark)::-webkit-scrollbar-thumb {
                border-color: var(--theme);
            }
        }

    </style>
</noscript><!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>MathJax 移动端优化</title>
    <script>
        MathJax = {
            tex: {
                displayMath: [['\\[', '\\]'], ['$$', '$$']],
                inlineMath: [['\\(', '\\)'], ['$', '$']],
                processEscapes: true,
                processEnvironments: true,
                tags: 'ams',
                packages: {'[+]': ['boldsymbol', 'ams']}
            },
            chtml: {
                scale: 1,
                minScale: 0.8,
                matchFontHeight: false,
                displayAlign: 'center',
                displayIndent: '0em',
                mtextInheritFont: false,
                merrorInheritFont: true,
                mathmlSpacing: false,
                skipHtmlTags: ['script','noscript','style','textarea','pre','code','a'],
                ignoreHtmlClass: 'tex2jax_ignore',
                processHtmlClass: 'tex2jax_process'
            },
            svg: {
                scale: 1,
                minScale: 0.8,
                mtextInheritFont: false,
                merrorInheritFont: true,
                mathmlSpacing: false,
                skipHtmlTags: ['script','noscript','style','textarea','pre','code','a'],
                ignoreHtmlClass: 'tex2jax_ignore',
                processHtmlClass: 'tex2jax_process'
            },
            options: {
                enableMenu: true,
                menuOptions: {
                    settings: {
                        zoom: 'Click'
                    }
                }
            },
            loader: {
                load: ['ui/safe', 'a11y/assistive-mml', '[tex]/boldsymbol', '[tex]/ams']
            },
            startup: {
                ready() {
                    MathJax.startup.defaultReady();
                    
                    
                    const observer = new ResizeObserver(entries => {
                        MathJax.typesetPromise();
                    });
                    observer.observe(document.body);
                    
                    
                    if (window.innerWidth <= 768) {
                        adjustForMobile();
                    }
                    
                    
                    window.addEventListener('orientationchange', function() {
                        setTimeout(() => {
                            MathJax.typesetPromise();
                            if (window.innerWidth <= 768) {
                                adjustForMobile();
                            }
                        }, 300);
                    });
                }
            }
        };
        
        function adjustForMobile() {
            
            document.querySelectorAll('mjx-container').forEach(el => {
                el.style.maxWidth = '100%';
                el.style.overflowX = 'auto';
                el.style.webkitOverflowScrolling = 'touch';
            });
        }
    </script>
    <script id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js"></script>
</head>

</head>

<body class="" id="top">
<script>
    if (localStorage.getItem("pref-theme") === "dark") {
        document.body.classList.add('dark');
    } else if (localStorage.getItem("pref-theme") === "light") {
        document.body.classList.remove('dark')
    } else if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
        document.body.classList.add('dark');
    }

</script>

<header class="header">
    <nav class="nav">
        <div class="logo">
            <a href="http://localhost:1313/WangJV-Blog-Pages/" accesskey="h" title="WangJV Blog (Alt + H)">WangJV Blog</a>
            <div class="logo-switches">
                <button id="theme-toggle" accesskey="t" title="(Alt + T)" aria-label="Toggle theme">
                    <svg id="moon" xmlns="http://www.w3.org/2000/svg" width="24" height="18" viewBox="0 0 24 24"
                        fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
                        stroke-linejoin="round">
                        <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
                    </svg>
                    <svg id="sun" xmlns="http://www.w3.org/2000/svg" width="24" height="18" viewBox="0 0 24 24"
                        fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
                        stroke-linejoin="round">
                        <circle cx="12" cy="12" r="5"></circle>
                        <line x1="12" y1="1" x2="12" y2="3"></line>
                        <line x1="12" y1="21" x2="12" y2="23"></line>
                        <line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
                        <line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
                        <line x1="1" y1="12" x2="3" y2="12"></line>
                        <line x1="21" y1="12" x2="23" y2="12"></line>
                        <line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
                        <line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
                    </svg>
                </button>
            </div>
        </div>
        <ul id="menu">
            <li>
                <a href="http://localhost:1313/WangJV-Blog-Pages/" title="Home">
                    <span>Home</span>
                </a>
            </li>
            <li>
                <a href="http://localhost:1313/WangJV-Blog-Pages/posts/" title="Posts">
                    <span>Posts</span>
                </a>
            </li>
            <li>
                <a href="http://localhost:1313/WangJV-Blog-Pages/archives/" title="Archive">
                    <span>Archive</span>
                </a>
            </li>
            <li>
                <a href="http://localhost:1313/WangJV-Blog-Pages/tags/" title="Tags">
                    <span>Tags</span>
                </a>
            </li>
            <li>
                <a href="http://localhost:1313/WangJV-Blog-Pages/search/" title="🔍 Search (Alt &#43; /)" accesskey=/>
                    <span>🔍 Search</span>
                </a>
            </li>
        </ul>
    </nav>
</header>
<main class="main">

<article class="post-single">
  <header class="post-header">
    <div class="breadcrumbs"><a href="http://localhost:1313/WangJV-Blog-Pages/">Home</a>&nbsp;»&nbsp;<a href="http://localhost:1313/WangJV-Blog-Pages/posts/">Posts</a></div>
    <h1 class="post-title entry-hint-parent">
      EWA Splatting
      <span class="entry-hint" title="Draft">
        <svg xmlns="http://www.w3.org/2000/svg" height="35" viewBox="0 -960 960 960" fill="currentColor">
          <path
            d="M160-410v-60h300v60H160Zm0-165v-60h470v60H160Zm0-165v-60h470v60H160Zm360 580v-123l221-220q9-9 20-13t22-4q12 0 23 4.5t20 13.5l37 37q9 9 13 20t4 22q0 11-4.5 22.5T862.09-380L643-160H520Zm300-263-37-37 37 37ZM580-220h38l121-122-18-19-19-18-122 121v38Zm141-141-19-18 37 37-18-19Z" />
        </svg>
      </span>
    </h1>
    <div class="post-meta"><span title='2024-06-09 17:13:56 +0800 CST'>June 9, 2024</span>&nbsp;·&nbsp;5 min&nbsp;·&nbsp;950 words&nbsp;·&nbsp;WangJV&nbsp;|&nbsp;<a href="https://github.com/WangJV0812/WangJV-Blog-Source/tree/master/content/posts/EWA%20Splatting/index.md" rel="noopener noreferrer edit" target="_blank">Suggest Changes</a>

</div>
  </header> <div class="toc">
    <details >
        <summary accesskey="c" title="(Alt + C)">
            <span class="details">Table of Contents</span>
        </summary>

        <div class="inner"><nav id="TableOfContents">
  <ul>
    <li><a href="#1-介绍">1. 介绍</a></li>
    <li><a href="#2-相关工作">2. 相关工作</a></li>
    <li><a href="#3-理想采样">3. 理想采样</a>
      <ul>
        <li><a href="#31-采样和混叠">3.1. 采样和混叠</a></li>
        <li><a href="#32-抗混叠">3.2. 抗混叠</a></li>
        <li><a href="#33-渲染和重采样">3.3. 渲染和重采样</a></li>
      </ul>
    </li>
    <li><a href="#4-体渲染中的重采样过程">4. 体渲染中的重采样过程</a>
      <ul>
        <li><a href="#41-splatting-算法">4.1. Splatting 算法</a></li>
        <li><a href="#42-体渲染滤波器">4.2. 体渲染滤波器</a></li>
      </ul>
    </li>
    <li><a href="#5-表面重采样">5. 表面重采样</a>
      <ul>
        <li><a href="#51-属性方程的表面采样">5.1. 属性方程的表面采样</a></li>
        <li><a href="#52-表面重采样滤波器">5.2. 表面重采样滤波器</a></li>
      </ul>
    </li>
    <li><a href="#6-ewa-重采样滤波器">6. EWA 重采样滤波器</a>
      <ul>
        <li><a href="#61-椭圆高斯滤波器">6.1. 椭圆高斯滤波器</a></li>
        <li><a href="#62-ewa-体渲染重采样">6.2. EWA 体渲染重采样</a></li>
        <li><a href="#63-ewa-表面重采样滤波器">6.3. EWA 表面重采样滤波器</a></li>
      </ul>
    </li>
  </ul>
</nav>
        </div>
    </details>
</div>

  <div class="post-content"><h2 id="1-介绍">1. 介绍<a hidden class="anchor" aria-hidden="true" href="#1-介绍">#</a></h2>
<p>体渲染是科学和工程应用中可视化获取和模拟数据集的一项重要技术。理想的体渲染算法在三维空间中重建一个连续函数，将这个三维函数转换到相机空间中，然后沿着视线计算中间中连续函数的不透明度积分。1989年，Westover引入了用于交互式体渲染的铺摊算法，该算法近似实现了这一过程。铺摊算法将体数据视为一组吸收和发射光线的粒子。该积分预先在每个粒子上计算，形成足迹函数。每个足迹（或铺摊）在图像平面上传播其贡献。这些贡献从后到前组合成最终图像。另一方面，激光测距和基于图像的扫描技术产生了一些迄今为止最复杂和最引人注目的图形模型，导致了大量的表面点样本。常用的方法是从点数据生成三角网格，并使用网格简化技术进行渲染。</p>
<p>相比之下，最近的工作重点是直接渲染没有连通性的点样本。大多数这些方法基于类似于体渲染中的铺摊方法。在本文中，我们提出了一种高质量铺摊的框架。我们的推导与Heckbert的椭圆加权平均（EWA）纹理滤波器的方法相似，因此我们称我们的算法为EWA铺摊。EWA铺摊的主要特点是它集成了一个椭圆高斯重建核和一个低通滤波器，因此在输出图像中防止了混叠伪影，同时避免了过度模糊。此外，我们使用相同的框架来推导体数据和表面数据的铺摊原语。EWA体铺摊使用任意椭圆高斯重建核，并有效支持透视投影。我们的方法基于一种计算足迹函数的新方法，该方法依赖于将体数据转换到所谓的射线空间。这种转换等同于透视投影。通过在每个体素上使用其局部仿射近似，我们推导出EWA足迹在屏幕空间中的解析表达式。</p>
<p>EWA体铺摊原语可以很容易地集成到常规体铺摊算法中。由于其灵活性，它可以用于渲染直线型、曲线型或非结构化的体数据集。足迹的光栅化使用前向差分进行，只需一个一维足迹表即可用于所有重建核和任何视角。EWA表面铺摊等同于三角形渲染管线的EWA纹理滤波器的屏幕空间形式。因此，它为点采样表面提供了高质量的各向异性纹理滤波。</p>
<p>我们将展示，通过将高斯体重建核减少到表面重建核，可以从EWA体铺摊推导出EWA表面铺摊。因此，EWA铺摊是体数据和表面数据的通用渲染原语。例如，我们可以通过沿体积梯度压平三维高斯核来执行高质量的等值面渲染。本文组织如下：第二部分讨论以前的工作。第三部分回顾信号处理理论中的基本结果，这些结果是分析混叠所需的。我们还介绍了理想重采样滤波器的一般概念，该滤波器通过结合重建核和低通滤波器防止在渲染过程中出现混叠。接下来，在第四部分中，我们描述如何将体渲染建模为一个重采样过程，从而得出理想的体重采样滤波器。同样，我们在第五部分中推导出渲染点采样表面的理想重采样滤波器。在第六部分中，我们介绍了EWA重采样滤波器，该滤波器使用椭圆高斯作为重建核和低通滤波器。我们给出了EWA体重采样滤波器和EWA表面重采样滤波器的明确公式。此外，我们展示了如何通过压平体重建核，将表面重采样滤波器作为体重采样滤波器的特例来推导。最后，第七和第八部分讨论了我们的实现和结果，第九部分总结了本文。</p>
<h2 id="2-相关工作">2. 相关工作<a hidden class="anchor" aria-hidden="true" href="#2-相关工作">#</a></h2>
<p>在体渲染的相关文章中，关于散点技术的最初工作是由Westover提出的。基本的体散点算法在从后向前合成散点时由于可见性确定不准确而受到影响，这导致了明显的伪影，例如颜色渗漏。后来，Westover使用轴对齐的片缓冲区解决了这个问题。然而，这种技术在动画中容易出现令人不快的跳跃伪影。最近，Mueller和Crawfis提出了将片缓冲区与图像平面平行对齐，而不是与体数据的轴平行对齐。此外，他们分别散射每个重建核的几个切片。这种技术类似于基于切片的体渲染，不会出现跳跃伪影。Mueller和Yagel结合了散点和光线投射技术，以加速透视投影的渲染。Laur和Hanrahan描述了一种分层散点算法，使渲染过程中的逐步细化成为可能。此外，Lippert和Gross引入了一种直接使用体数据的小波表示的散点算法。</p>
<p>如果三维核不是径向对称的，例如对于直线网格、曲线网格或不规则网格，则需要特别注意。此外，对于三维空间中的任意位置，所有核的贡献必须相加为一。否则，图像中会出现斑点等伪影。对于直线网格，Westover提出了使用椭圆足迹，然后将其扭曲回圆形足迹的方案。为了渲染曲线网格，Mao使用随机泊松重采样生成一组新点，这些点的核是球形或椭圆形的。他计算椭圆足迹的方法与Westover十分相似。如第6.2节所述，我们的技术可以用于直线网格、曲线网格和不规则网格，以高效准确地投影和光栅化椭圆散点核。Westover的原始框架没有处理透视投影引起的采样率变化。在体数据中，发散射线的采样率低于体网格采样率的区域可能会出现混叠伪影。体散点中的混叠问题首先由Swan等和Mueller等解决。他们通过依距离伸展足迹，使其充当低通滤波器。相反，EWA散点在一个统一的框架中建模了重建和带限纹理函数的功能。</p>
<p>Levoy和Whitted的开创性报告引入了将表面表示为点集并将其作为渲染原语的概念。由于几何复杂性的不断增加，他们的想法最近引起了更多的关注。QSplat是一个点渲染系统，旨在交互式渲染由现代扫描设备生成的大型数据集。其他研究人员展示了基于点的方法在渲染几何复杂对象方面的效率。在某些系统中，点基表示暂时存储在渲染管线中以加速渲染。我们在工作中系统地解决了在点采样对象上表示纹理函数并避免渲染时混叠的问题。表面散点技术可以替代以前方法中使用的启发式方法，并提供更优质的纹理质量。我们沿着Heckbert的开创性工作发展了EWA散点，他引入了EWA滤波以避免表面纹理的混叠。我们最近扩展了他的框架，以表示和渲染不规则点采样表面上的纹理函数和体散点。第6.4节将展示EWA体散点和表面散点之间的联系。</p>
<h2 id="3-理想采样">3. 理想采样<a hidden class="anchor" aria-hidden="true" href="#3-理想采样">#</a></h2>
<h3 id="31-采样和混叠">3.1. 采样和混叠<a hidden class="anchor" aria-hidden="true" href="#31-采样和混叠">#</a></h3>
<p>混叠是计算机图形学中的一个基本问题。尽管从概念上讲，计算机图形学通常处理图形模型的连续表示，但在实际操作中，计算机生成的图像是由离散的样本数组表示的。图像合成涉及连续和离散表示之间的转换，这可能会导致混叠伪影，例如如图1所示的摩尔纹和锯齿边缘，或动画中的闪烁。</p>
<p>为了研究混叠问题，将图像、表面纹理或体数据解释为多维信号是有用的。在接下来的讨论中，我们将重点讨论一维信号，并在第4和第5节中返回多维信号。当一个连续信号被转换为离散信号时，它将在一个离散网格上进行评估或采样。为了分析采样的效果并理解信号的连续表示和离散表示之间的关系，我们回顾一些信号处理理论中的定义和结果。滤波器是一种将信号作为输入并生成修改后的信号或响应作为输出的过程。最容易理解的一类滤波器是线性空间不变滤波器。线性空间不变滤波器L由其脉冲响应 $h(x)$​ 唯一表征，即其对脉冲输入的输出。因此，线性空间不变滤波器对任何输入信号f(x)的响应由f(x)和h(x)的卷积给出。
</p>
$$
 \mathcal{L}\{f(x)\}=\int_{-\infty}^{+\infty}f(t)h(x-t)dt=(f\otimes h)(x).
$$<p>
一个基本的的分析滤波器的方法是计算滤波器的特征值和特征函数。线性时不变滤波器的特征函数是一个复指数函数，特征值由冲击响应的傅立叶变换给出，这被称为频率响应。信号 $f(x)$ 的傅立叶变换被成为该信号的频谱，定义为 $F(\omega)$。其中 $\omega$​ 是频率。对于连续时间信号而言，我们往往使用傅立叶变换和频域来分析。在采样一个连续信号时，我们将被采样的信号与一个脉冲序列相乘，但是采样的过程会导致在频域上产生混叠。为了重建原始连续信号，我们需要滤除所有重复的信号。如果混叠的信号没有重叠，可以通过一个门滤波器来实现。</p>
<p><img alt="aligned" loading="lazy" src="http://localhost:1313/WangJV-Blog-Pages/2024/06/ewa-splatting/images/image-20240601113528861.png"></p>
<h3 id="32-抗混叠">3.2. 抗混叠<a hidden class="anchor" aria-hidden="true" href="#32-抗混叠">#</a></h3>
<p>根据以上讨论，我们得出结论，有两种方法可以减少混叠问题：我们可以通过更高频率对连续信号进行采样，或者在采样之前消除高于奈奎斯特限的频率，这称为预滤波。由于大多数感兴趣的信号不是带限的，以更高频率进行采样虽然可以缓解，但不能完全避免混叠。此外，增加采样频率会导致大多数算法的内存和计算要求增加。另一方面，预滤波是通过在采样之前对信号应用低通滤波器来实现的，因此它是更为理论上合理的抗混叠方法。使用截止频率为 $\omega_s=2$ 的理想低通滤波器，滤波后的信号将被带限制在采样网格的奈奎斯特频率内，因此可以精确重建。实际上，预滤波是作为空间域中的卷积来实现的，因此，为了提高效率，需要使用支持范围较小的预滤波器。然而，滤波器在空间域和频域的宽度是反比关系，因此在采样过程中不可避免地会出现一些混叠</p>
<h3 id="33-渲染和重采样">3.3. 渲染和重采样<a hidden class="anchor" aria-hidden="true" href="#33-渲染和重采样">#</a></h3>
<p>在我们的框架中，图形模型表示为描述对象属性（如体积不透明度或表面纹理）的多维函数的不规则间隔样本集。我们通过计算加权和来重建连续的属性函数。
</p>
$$
f_c(u)= \sum_{k\in \mathbb{IN}}\omega_kr_k(u)
$$<p>
其中 $r_k$ 被称为以采样点 $u_k$ 为中心重建核；$\omega_k$ 是采样的值，即在 $u_k$ 点的颜色。我们定义原始的世界为 $f(u_k)$​。我们将属性方程渲染出来的过程表述为一个重采样过程，包含三个步骤。</p>
<p><img alt="aligned" loading="lazy" src="http://localhost:1313/WangJV-Blog-Pages/2024/06/ewa-splatting/images/image-20240601121527646.png"></p>
<ol>
<li>将 $f_c(u)$ 投影到屏幕空间上，产生连续的屏幕空间信号 $g_c(u)$：</li>
</ol>
$$
g_c(\mathbf{x})=\{\mathcal{P}(f_c)\}(\mathbf{x}),
$$<p>其中 $x$ 是二维相机坐标系的坐标，并且投影过程被定义为一个投影算子 $\mathcal{P}$，我们可以用重建核的形式来重写上面的投影过程：
</p>
$$
g_c(\mathbf{x})=\left\{\mathcal{P}{\left(\sum_{k\in\mathrm{IN}}w_kr_k\right)}\right\}(\mathbf{x})=\sum_{k\in\mathrm{IN}}w_kp_k(\mathbf{x}),
$$<p>
我们将投影过程的重建核简写为$p_k = \mathcal{P}_k$。</p>
<ol start="2">
<li>将屏幕空间上的信号的幅值限制到屏幕能表达的范围内：</li>
</ol>
$$
g_c^{\prime}(\mathbf{x})=g_c(\mathbf{x})\otimes h(\mathbf{x})=\int_{\mathbf{IR}^2}g_c(\boldsymbol{\eta})h(\mathbf{x}-\boldsymbol{\eta})d\boldsymbol{\eta}.
$$<ol start="3">
<li>让屏幕空间上的连续信号和一个采样序列相乘使其离散化。</li>
</ol>
$$
g(x)=g'(x)i(x)
$$<p>将上述关系式按相反顺序展开，即可得出投影连续输出函数的明确表达式：</p>
$$
\begin{aligned}
g_c'(\mathbf{x})& =\int_{\text{IR}^2}\left\{\mathcal{P}\left(\sum_{k\in\text{IN}}w_kr_k\right)\right\}(\boldsymbol{\eta})h(\mathbf{x}-\boldsymbol{\eta})d\boldsymbol{\eta}  \\
&=\sum_{k\in\mathbf{IN}}w_k\int_{\mathbf{IR}^2}p_k(\eta)h(\mathbf{x}-\eta)d\boldsymbol{\eta} \\
&=\sum_{k\in\mathbf{IN}}w_k\rho_k(\mathbf{x}), \\
&\mathrm{where}\quad\rho_k(\mathbf{x})=(p_k\otimes h)(\mathbf{x}).
\end{aligned}
$$<p>我们将投影和滤波器的重建核 $\rho_k$ 为重采样核，在屏幕空间内表达为一个卷积。</p>
<h2 id="4-体渲染中的重采样过程">4. 体渲染中的重采样过程<a hidden class="anchor" aria-hidden="true" href="#4-体渲染中的重采样过程">#</a></h2>
<p>我们将体渲染分为两种基本方式：反向投影算法和正向投影算法。反向投影算法算法指的是价从像素平面出发射出一条射线穿过体积数据；正向投影算法则反过来，他将空间中的属性投影到像素平面上。正向投影算法包含一系列中间步骤，这些步骤包含很多数据的坐标变换和，就像传统的渲染管线一样。图2展示了正向投影算法的管线。</p>
<p><img alt="pipline" loading="lazy" src="http://localhost:1313/WangJV-Blog-Pages/2024/06/ewa-splatting/images/image-20240601123630793.png"></p>
<p>综上所述，我们将简要介绍与我们的技术相关的坐标系和变换。体积数据最初是在源空间中给出的，在这里通常称为对象空间。要从任意视角渲染数据，首先要使用查看变换将其映射到摄像机空间。摄像机坐标系的定义是，其原点位于投影的中心点。</p>
<p>我们将数据进一步转换到射线空间，第 4.1 节将介绍射线空间。射线空间是一种非笛卡尔坐标系，可以方便地计算体积渲染方程。在射线空间中，观察射线平行于坐标轴，便于对体积函数进行解析积分。我们将在第 6.2 节介绍从摄像机空间到射线空间的转换；这是我们技术的关键要素。由于其目的类似于 OpenGL 等渲染管道中使用的投影变换，因此也称为投影映射。对体积渲染方程进行求值后，会在屏幕空间中生成一个二维图像。最后，该图像将转换为视口坐标。我们的重点是技术的基本方面，因此在下面的解释中不会涉及视口变换。不过，我们可以很容易地将其纳入到实现过程中。</p>
<h3 id="41-splatting-算法">4.1. Splatting 算法<a hidden class="anchor" aria-hidden="true" href="#41-splatting-算法">#</a></h3>
<p>我们用一个三维列向量来定义一条射线上的空间点。给定投影中心和投影平面，这三个坐标在几何上解释如下：坐标 \(x_0\) 和 \(x_1\) 指定投影平面上的一个点。与投影中心和投影平面上的点 $(x_0, x_1)$ 相交的射线称为视线。使用缩写 $x = (x_0, x_1)^T$，我们也将通过 $(x_0, x_1)$ 的视线称为 $x$。第三个坐标 $x_2$ 指定从投影中心到视线上某一点的欧几里得距离。请注意，我们的符号不区分射线 $x$ 和射线空间中的一个点 $x$；然而，从上下文中可以清楚地知道所指的是哪一个。此外，为了简化符号，我们将使用 $x$、$(x, x_2)^T$ 或 $(x_0, x_1, x_2)^T$​ 的任何同义词来表示射线空间中的一个点。</p>
<p><img alt="volumn rendering" loading="lazy" src="http://localhost:1313/WangJV-Blog-Pages/2024/06/ewa-splatting/images/image-20240601130422076.png"></p>
<p>体积渲染方程描述了波长为 $\lambda$ 的光强度 $I_\lambda(x)$，该光强度沿长度为 $L$ 的射线 $x$ 到达投影中心。</p>
$$
I_\lambda(\mathbf{x})=\int_0^Lc_\lambda(\mathbf{x},\xi)f_c^{\prime}(\mathbf{x},\xi)e^{-\int_0^\xi f_c^{\prime}(\mathbf{x},\mu)d\mu}d\xi,
$$<p>其中，$\tau_c(x)$ 是定义光遮挡率的消光函数，$c_\lambda(x)$ 是发射系数。指数项可以解释为衰减因子。最后，乘积 $c_\lambda(x) \tau_c(x)$ 也称为源项，描述在点 $x_2$ 处沿射线 $x$ 方向散射的光强度。在接下来的方程中，我们将省略参数 $\lambda$，意味着（7）需要对不同波长分别进行评估。现在，我们假设物体空间（即源空间）中的消光函数 $\tau_c(u)$ 以（1）的形式给出，即系数 $w_k$ 和重建核 $r_k(u)$ 的加权和。这对应于一个物理模型，其中体积由吸收和发射光的单个粒子组成。重建核 $r_k$ 反映了单个粒子的位置信息和形状。粒子可以不规则地分布，形状也可能不同；因此，该模型不限于规则数据集。请注意，射线空间中的消光函数 $\tau_c(x)$ 通过将物体空间到相机空间的映射 $\phi$ 和相机空间到射线空间的映射 $\psi$ 连接起来计算得出，即：</p>
$$
f_c^{\prime}(\mathbf{x})=f_c(\varphi^{-1}(\phi^{-1}(\mathbf{x})))=\sum_kw_kr_k^{\prime}(\mathbf{x}),
$$<p>其中，$r'_k(x) = r_k(\phi^{-1}(\psi^{-1}(x)))$ 是射线空间中的重建核。映射 $\psi$ 和 $\phi$ 将在第6.2节中详细讨论。由于积分的线性，可得到：</p>
$$
I(\mathbf{x})=\sum_kw_k\bigg(\int_0^Lc(\mathbf{x},\xi)r_k^{\prime}(\mathbf{x},\xi)\\\prod_je^{-w_j\int_0^\xi r_j^{\prime}(\mathbf{x},\mu)d\mu}d\xi\bigg),
$$<p>这可以解释为投影重建核的加权和。因此，根据（3），我们有对应关系 $I \approx \sum_{k} P_k w_k p_k \approx g_c$，为了与第3节保持一致，我们从现在开始将使用 $g_c$。为了数值计算 $g_c$，splatting算法做了几个简化假设，如图5右侧所示。通常，重建核 $r_0^k(x)$ 有局部支撑。splatting方法假设沿射线 $x$ 的这些局部支撑区域不重叠，并且重建核按前后顺序排序。我们还假设沿射线上每个重建核的支撑中发射系数是恒定的；因此，我们使用符号 $c_k(x_0, x_1) \approx c(x_0, x_1, x_2)$，其中 $(x_0, x_1, x_2)$ 在 $r_0^k$ 的支撑中。此外，我们用指数函数的前两项泰勒展开近似替换指数函数，因此 $e^x \approx 1 + x$。最后，我们忽略自遮挡。利用这些假设，我们重写（9），得到：</p>
$$
g_c(\mathbf{x})=\sum_kw_kc_k(\mathbf{x})q_k(\mathbf{x})\prod_{j=0}^{k-1}\bigl(1-w_jq_j(\mathbf{x})\bigr),
$$<p>其中 $q_k(x)$ 表示一个积分后的重建核，因此：</p>
$$
q_k(\mathbf{x})=\int_{\mathbf{IR}}r_k^{\prime}(\mathbf{x},x_2)dx_2.
$$<p>上述公式是所有splatting算法的基础。Westover [2] 提出了术语“足迹函数”来表示积分后的重建核 $q_k$。足迹函数是一个二维函数，指定了三维核对图像平面上每个点的贡献。由于沿视线对体积进行积分类似于将表面上的点投影到图像平面上，因此坐标 $x = (x_0, x_1)^T$ 也称为屏幕坐标，并且我们说 $g_c(x)$ 和 $q_k(x)$ 在屏幕空间中定义。</p>
<p>Splatting具有高效性，这归功于使用预积分的重建核。因此，在体积积分过程中，沿着视线的每个采样点都使用2D卷积进行计算。相比之下，射线投射方法需要对每个采样点进行3D卷积。这使得splatting算法在渲染效率上具有固有优势。此外，splatting有助于使用比射线投射通常使用的三线性核更大的范围的高质量核。另一方面，基本splatting方法由于错误的可见性确定而受到众多影响。这个问题不可避免地由重建核不重叠且按顺序从后到前排列的假设引入。</p>
<h3 id="42-体渲染滤波器">4.2. 体渲染滤波器<a hidden class="anchor" aria-hidden="true" href="#42-体渲染滤波器">#</a></h3>
<p>体渲染方程所表述的图片是一个像平面上的连续信号。为了将该函数正确采样到离散输出图像而没有混叠伪影，必须对其进行带限处理以匹配离散图像的奈奎斯特频率。我们通过将 $g_c(x)$ 与适当的低通滤波器 $h(x)$ 进行卷积来实现这一带限处理，从而得到抗混叠的splatting方程：</p>
$$
g_c^{\prime}(\mathbf{x})=(g_c\otimes h)(\mathbf{x})=\sum_kw_k\int_{\mathrm{IR}^2}c_k(\eta)q_k(\eta)\\\prod_{j=0}^{k-1}(1-w_jq_j(\eta))h(\mathbf{x}-\eta)d\eta.
$$<p>不幸的是，由于发射和衰减项，不能显式地计算（13）中的卷积积分。因此，我们做出两个简化假设以对其进行重排，从而得到一个可以高效评估的近似值。首先，我们假设在每个足迹函数 $q_k$ 的支撑区域内，发射系数近似为常数，因此对于支撑区域内的所有 $x$，$c_k(x) \approx c_k$。结合假设沿视线上每个重建核的支撑区域内发射系数是常数，这意味着发射系数在每个重建核的完整三维支撑区域内是常数。换句话说，这对应于每体素评估的着色模型或预着色，忽略抗混叠的着色效果。请注意，表面纹理的预滤波方法通常也忽略了由着色引起的混叠。此外，我们假设在每个足迹函数的支撑区域内，衰减因子有一个近似为常数的值 $o_k$。因此：</p>
$$
\prod_{j=0}^{k-1}(1-w_jq_j(\mathbf{x}))\approx o_k
$$<p>对于支撑区域内的所有 $x$，衰减因子的变化表明足迹函数被体积数据中更不透明的区域部分覆盖。因此，这种变化可以解释为一个“软”边缘。忽略这种情况意味着我们无法防止边缘混叠。同
样，这类似于渲染表面，其中边缘和纹理混叠由不同的算法处理。利用这些简化，我们可以将 (13) 重写为：</p>
$$
\begin{gathered}
(g_c\otimes h)(\mathbf{x}) \approx\sum_kw_kc_ko_k\int_{\mathbf{IR}^2}q_k(\eta)h(\mathbf{x}-\eta)d\eta  \\
=\sum_kw_kc_ko_k(q_k\otimes h)(\mathbf{x}). 
\end{gathered}
$$<p>我们可以通过重建核的形式写出：</p>
$$
\rho_k(\mathbf{x})=c_ko_k(q_k\otimes h)(\mathbf{x})=(p_k\otimes h)(\mathbf{x})
$$<p>一个理想的体积重采样滤波器，将投影重建核 $p_k \approx c_k o_k q_k$ 和低通滤波器 $h$ 结合在一起。因此，我们可以通过用重采样滤波器 $\Phi_k$ 替换原始splatting方程（11）中的足迹函数 $q_k$ 来近似抗混叠的splatting方程（13）。这意味着我们不是直接对输出函数 $g_c(x)$ 进行带限处理，而是分别对每个足迹函数进行带限处理。根据上述假设，我们得到一个splatting算法，该算法生成尊重栅格图像奈奎斯特频率的带限输出函数，从而避免了混叠伪影。请记住，重建核在射线空间中进行积分，导致足迹函数在体积中大小和形状变化显著。因此，方程（15）中的重采样滤波器是强空间变异的。该技术基于重建核的统一缩放以对消光函数进行带限处理。对于径向对称的核，他们的技术产生了与我们的方法相似的结果。然而，对于更一般的核，例如椭圆核，统一缩放是理想低通滤波的较差近似。引入额外的模糊性也无法避免混叠伪影。另一方面，我们的方法在这些情况下提供非均匀缩放，从而带来更高的图像质量，如第8节所示。此外，我们上面的分析表明，带限消光函数并不能保证无混叠图像。由于着色和边缘的存在，奈奎斯特极限以上的频率仍然存在。</p>
<h2 id="5-表面重采样">5. 表面重采样<a hidden class="anchor" aria-hidden="true" href="#5-表面重采样">#</a></h2>
<h3 id="51-属性方程的表面采样">5.1. 属性方程的表面采样<a hidden class="anchor" aria-hidden="true" href="#51-属性方程的表面采样">#</a></h3>
<p>我们将点采样表面表示为三维物体空间中一组不规则间隔的点 $\{P_k\}$，这些点没有连接性。一个点 $P_k$ 具有位置和法线。它与重建核 $r_k$ 以及属性函数的采样值相关联，例如，表示红、绿、蓝色分量的连续函数的 $w_{r_k}$、$w_{g_k}$、$w_{b_k}$。在不失一般性的情况下，我们用标量系数 $w_k$ 进行所有进一步的计算。请注意，基函数 $r_k$ 和系数 $w_k$ 可以在预处理步骤中确定我们。定义了一个由点集表示的表面上的连续函数，如图6所示。在物体空间中的表面上任意一点 $Q$（左图所示），我们在 $Q$ 的小邻域内构建了表面的局部参数化，如右图所示。点 $Q$ 和 $P_k$ 分别具有局部源空间坐标 $u$ 和 $u_k$。使用参数化，我们可以像在（1）中那样在表面上定义连续属性函数 $f_c(u)$。</p>
$$
f_c(\mathbf{u})=\sum_{k\in\mathbb{IN}}w_kr_k(\mathbf{u}).
$$<p>我们将选择具有局部支撑或适当截断的基函数 $r_k$。然后，$u$ 位于少数基函数的支撑中。请注意，为了评估（15），局部参数化只需在这些支撑区域的并集内建立，这个区域非常小。此外，如第7.2节所述，我们将在渲染过程中动态计算这些局部参数化。</p>
<p><img alt="2D Gaussian filter" loading="lazy" src="http://localhost:1313/WangJV-Blog-Pages/2024/06/ewa-splatting/images/image-20240601143824888.png"></p>
<h3 id="52-表面重采样滤波器">5.2. 表面重采样滤波器<a hidden class="anchor" aria-hidden="true" href="#52-表面重采样滤波器">#</a></h3>
<p>渲染参数化表面涉及将属性函数 $f_c(u)$ 从参数空间（即源空间）映射到屏幕空间。如图7所示，我们用 $x = m(u)$ 表示这个从二维到二维的映射。它由从源空间到物体空间的二维到三维的参数化以及从物体空间到屏幕空间的三维到二维的投影组成，详细描述见第6.3节。使用 $m$，我们可以将连续输出函数写为：
</p>
$$
g_c(\mathbf{x})=\sum_kw_kc(\mathbf{x})r_k^\prime(\mathbf{x}),
$$<p><img alt="geometry transformation" loading="lazy" src="http://localhost:1313/WangJV-Blog-Pages/2024/06/ewa-splatting/images/image-20240601143622042.png"></p>
<p>其中 $r_k'(x) = r_k(m^{-1}(x))$ 是映射到屏幕空间的重建核。如第4节所述，$c$ 是由于表面着色而产生的发射项。同样，我们假设在屏幕空间中每个 $r_k'$ 的支撑区域内发射是恒定的，这相当于每点着色，因此忽略了由于着色引起的混叠。因此，根据（4），带限输出函数为：</p>
$$
(g_c\otimes h)(\mathbf{x})=\sum_kw_kc_k(r_k^{\prime}\otimes h)\mathbf{x}).
$$<p>按照采样核的形式，我们可以写作：</p>
$$
\rho_k(\mathbf{x})=c_k(r_k^{\prime}\otimes h)(\mathbf{x})=(p_k\otimes h)(\mathbf{x})
$$<h2 id="6-ewa-重采样滤波器">6. EWA 重采样滤波器<a hidden class="anchor" aria-hidden="true" href="#6-ewa-重采样滤波器">#</a></h2>
<p>对于体积和表面渲染，我们选择椭圆高斯函数作为重建核和低通滤波器，因为它们提供了对我们技术至关重要的某些特性：高斯函数在仿射映射和卷积下封闭，并且沿一个坐标轴积分一个三维高斯函数会得到一个二维高斯函数。这些特性使我们能够解析地计算体积和表面重采样滤波器（分别为公式（14）和（18））为单个二维高斯函数，正如将在第6.2节和第6.3节中所展示的那样。在第6.1节中，我们总结了在以下章节推导中利用的高斯函数的数学特性。</p>
<h3 id="61-椭圆高斯滤波器">6.1. 椭圆高斯滤波器<a hidden class="anchor" aria-hidden="true" href="#61-椭圆高斯滤波器">#</a></h3>
<p>我们定义中心在 $x$，协方差为$V$ 3D 高斯椭圆函数 $\mathcal G^3_V(x-p)$ 为：</p>
$$
\mathcal{G}_{\mathbf{V}}^{3}(\mathbf{x}-\mathbf{p})=\frac{1}{\left(2\pi\right)^{3/2}\left|\mathbf{V}\right|^{{\frac{1}{2}}}}e^{{-\frac{1}{2}\left(\mathbf{x}-\mathbf{p}\right)^{T}\mathbf{V}^{-1}\left(\mathbf{x}-\mathbf{p}\right)}},
$$<p>其中 $|V|$ 是 $V$ 的行列式。在这种形式下，高斯函数被归一化为单位积分。对于三维高斯函数，$V$ 是一个对称的 $3 \times 3$ 矩阵，$x$ 和 $p$ 分别是列向量 $\mathbf{x} = (x_0, x_1, x_2)^T$ 和 $\mathbf{p} = (p_0, p_1, p_2)^T$。类似于上式，一个椭圆二维高斯函数 $G_{2V}(\mathbf{x} - \mathbf{p})$ 定义如下：</p>
$$
\mathcal{G}_{\mathbf{V}}^2(\mathbf{x}-\mathbf{p})=\frac1{2\pi|\mathbf{V}|^{\frac12}}e^{-\frac12(\mathbf{x}-\mathbf{p})^T\mathbf{V}^{-1}(\mathbf{x}-\mathbf{p})},
$$<p>对于二维和三维高斯函数，我们可以轻松地对任意维度 $n$ 的高斯函数 $G_{nV}$ 应用任意仿射映射 $\mathbf{u} = \phi(\mathbf{x})$。让我们将仿射映射定义为 $\phi(\mathbf{x}) = M\mathbf{x} + \mathbf{c}$，其中 $M$ 是一个 $n \times n$ 矩阵，$\mathbf{c}$ 是一个向量 $\mathbf{c} = (c_1, \ldots, c_n)^T$。我们替换 $\mathbf{x} = \phi^{-1}(\mathbf{u})$，得到：</p>
$$
\mathcal{G}_{\mathbf{V}}^n(\Phi^{-1}(\mathbf{u})-\mathbf{p})=\frac1{|\mathbf{M}^{-1}|}\mathcal{G}_{\mathbf{M}\mathbf{V}\mathbf{M}^T}^n(\mathbf{u}-\Phi(\mathbf{p})).
$$<p>此外，卷积两个方差矩阵分别为 $V$ 和 $Y$ 的高斯函数会得到另一个方差矩阵为 $V + Y$​ 的高斯函数：</p>
$$
(\mathcal{G}_{\mathbf{V}}^n\otimes\mathcal{G}_{\mathbf{Y}}^n)(\mathbf{x}-\mathbf{p})=\mathcal{G}_{\mathbf{V}+\mathbf{Y}}^n(\mathbf{x}-\mathbf{p}).
$$<p>最后，对归一化的三维高斯函数 $G_{3V}$ 沿着一个坐标轴积分会得到一个归一化的二维高斯函数 $G_{2^{\wedge}VV}$，因此：</p>
$$
\int_{{\mathrm{IR}}}\mathcal{G}_{{\mathbf{V}}}^{3}(\mathbf{x}-\mathbf{p})dx_{2}=\mathcal{G}_{{\mathbf{\hat{V}}}}^{2}(\mathbf{\hat{x}}-\mathbf{\hat{p}}),
$$<p>其中 $\hat{x}$ 表示 $\mathbf{x} = (x_0, x_1)^T$，$\hat{p}$ 表示 $\mathbf{p} = (p_0, p_1)^T$。通过略过第三行和第三列来从 $3 \times 3$ 矩阵 $V$ 得到 $2 \times 2$ 的方差</p>
$$
\mathbf{V}=\begin{pmatrix}a&b&c\\b&d&e\\c&e&f\end{pmatrix}\Leftrightarrow\begin{pmatrix}a&b\\b&d\end{pmatrix}=\mathbf{\hat{V}}.
$$<p>后文中，我们用 $\mathcal{G}_V$ 来定义 2D 和 3D 高斯函数，通过上下文可以搞明白这里指的是什么。</p>
<h3 id="62-ewa-体渲染重采样">6.2. EWA 体渲染重采样<a hidden class="anchor" aria-hidden="true" href="#62-ewa-体渲染重采样">#</a></h3>
<p>在本节中，我们首先描述了如何将任意椭圆高斯体积重建核从物体空间映射到射线空间。我们的推导结果是射线空间中核的解析表达式 $r_{0k}(x)$，如公式（8）所示。然后，我们将能够根据公式（11）对核进行解析积分，并根据公式（22）将足迹函数 $q_k$ 与高斯低通滤波器 $h$ 卷积，从而得到一个椭圆高斯重采样滤波器 $\Phi_k$​。</p>
<h4 id="621-观察变换">6.2.1. 观察变换<a hidden class="anchor" aria-hidden="true" href="#621-观察变换">#</a></h4>
<p>重建核最初是在源空间或物体空间中给出的，其坐标为 $\mathbf{u} = (u_0, u_1, u_2)^T$。如第4.1节所述，我们将物体空间中的高斯重建核表示为：</p>
$$
r_k(\mathbf{u}) = \frac{1}{\sqrt{(2\pi)^3 |V_k|}} e^{-\frac{1}{2} (\mathbf{u} - \mathbf{u}_k)^T V_k^{-1} (\mathbf{u} - \mathbf{u}_k)},
$$<p>其中 $\mathbf{u}_k$ 是物体空间中的体素位置。对于常规体数据集，方差矩阵 $V_k$ 通常是单位矩阵。对于直线数据集，它们是对角矩阵，其中矩阵元素包含每个坐标轴上体素之间的距离的平方。曲线和不规则网格通常需要被重新采样为更规则的结构。例如，Mao等人 [31] 描述了一种用于曲线体积的随机采样方法，并介绍了一种计算曲线体积方差矩阵的方法。我们用一个向量 $\mathbf{t} = (t_0, t_1, t_2)^T$ 表示相机坐标。物体坐标通过一个称为观察变换的映射 $\mathbf{t} = \phi(\mathbf{u})$ 转换为相机坐标。观察变换通常是由一个矩阵 $W$ 和一个平移向量 $\mathbf{d}$ 定义的仿射映射，即 $\phi(\mathbf{u}) = W\mathbf{u} + \mathbf{d}$。</p>
<h4 id="622-投影变换">6.2.2. 投影变换<a hidden class="anchor" aria-hidden="true" href="#622-投影变换">#</a></h4>
<p>我们将把观察变换与一个将相机坐标转换为射线坐标的投影变换连接起来，如图8所示。相机空间被定义为相机坐标系的原点位于投影中心，并且投影平面是平面 $t_2 = 1$。相机空间和射线空间通过映射 $\mathbf{x} = \phi(\mathbf{t})$ 相关联。根据第4.1节中对射线空间的定义，$\phi(\mathbf{t})$ 及其逆 $\phi^{-1}(\mathbf{t})$ 如下：</p>
$$
\begin{pmatrix}
x_0 \\
x_1 \\
x_2
\end{pmatrix} = \phi(\mathbf{t}) = \begin{pmatrix} \frac{t_0}{t_2} \\ \frac{t_1}{t_2} \\ k(\mathbf{t}) \end{pmatrix}
$$$$
\begin{pmatrix}
t_0 \\
t_1 \\
t_2
\end{pmatrix} = \phi^{-1}(\mathbf{x}) = \begin{pmatrix} x_0 l \\ x_1 l \\ l \end{pmatrix}
$$<p>其中 $l = k(x_0, x_1, 1)^T$。</p>
<p><img alt="projection" loading="lazy" src="http://localhost:1313/WangJV-Blog-Pages/2024/06/ewa-splatting/images/image-20240601150427365.png"></p>
<p>不幸的是，这些映射不是仿射的，因此我们无法直接应用（21）将重建核从相机空间变换到射线空间。为了解决这个问题，我们引入了投影变换的局部仿射近似 $\phi_k$。它由 $t_k$ 点处 $\phi$ 的泰勒展开的前两项定义：$\phi_k(t) = x_k + J_k \cdot (\mathbf{t} - \mathbf{t}_k)$，其中 $t_k$ 是相机空间中高斯的中心，$x_k = \phi(t_k)$ 是射线空间中对应的位置。Jacobian 矩阵 $J_k$ 由 $\phi$ 在点 $t_k$​ 处的偏导数给出：
</p>
$$
J_k = \frac{\partial \phi}{\partial \mathbf{t}} (\mathbf{t}_k) = \begin{pmatrix}
\frac{1}{t_{k,2}} & 0 & -\frac{t_{k,0}}{t_{k,2}^2} \\
0 & \frac{1}{t_{k,2}} & -\frac{t_{k,1}}{t_{k,2}^2} \\
0 & 0 & \frac{1}{t_{k,2}}
\end{pmatrix},
$$<p>其中 $l_0 = k(t_{k,0}, t_{k,1}, t_{k,2})^T$。从源到射线空间的复合映射的局部仿射近似 $x = m_k(u)$ 由 $t = \phi'(\mathbf{u})$ 和 $x = \phi_k(\mathbf{t})$ 的连接给出：</p>
$$
\begin{aligned}
\mathbf{x} = m_k(\mathbf{u}) &= \phi_k(\phi'(\mathbf{u})) \\
&= \phi_k(W\mathbf{u} + \mathbf{d}) \\
&= \phi_k(W\mathbf{u}) + \phi_k(\mathbf{d}) \\
&= J_kW\mathbf{u} + x_k + J_k(\mathbf{d} - \mathbf{t}_k).
\end{aligned}
$$<p>我们将 $u = m_k^{-1}(x)$ 代入（25），将重建核映射到射线空间，得到所需的表达式 $r_{0k}(x)$：</p>
$$
r_{0k}(x) = G(V_k(m^{-1}(x) - u_k)) \cdot \frac{1}{|\mathbf{W}^{-1}J_k^{\wedge}(\mathbf{W}^{-1})^T|} \cdot G(V_{0k}(x - m(u_k))),
$$<p>其中 $V_{0k}$ 是射线坐标中的方差矩阵。$V_{0k}$ 给出为：</p>
$$
V_{0k} = J_kWV_kW^T(J_k)^T。
$$<p>需要注意，对于均匀或直角数据集，乘积 $WV_kW^T$ 只需每帧计算一次，因为 $V_k$ 对所有体素都相同，而 $W$ 只在帧与帧之间变化。由于雅可比矩阵对于每个体素位置都不同，所以 $V_{0k}$ 必须针对每个体素重新计算，每个体素需要两次 $3 \times 3$ 矩阵乘法，即 $V_{0k} = J_k(WV_kW^T)(J_k)^T$。对于曲线或不规则的体积，每个重建核都有一个独立的方差矩阵 $V_k$。我们的方法能够高效处理这种情况，每个体素只需要额外一次 $3 \times 3$ 矩阵乘法，即 $V_{0k} = (J_kW)V_k(J_kW)^T$。</p>
<p>相比之下，之前的技术通过计算在屏幕空间中的投影范围，然后建立到圆形足迹表的映射来处理椭圆形核。然而，这个过程计算量巨大。如图9所示，局部仿射映射仅对通过 $t_k$ 或 $x_k$ 的射线是精确的。该图被夸大以显示精确映射中的非线性效应。仿射映射本质上将透视投影近似为斜交正交投影。</p>
<p>因此，平行线保持不变，近似误差随射线发散增加而增加。然而，通常情况下，这些误差不会导致视觉伪影，因为相交于重建核的射线扇具有由重建核局部支持引起的小开角。使用透视投影进行分层的一种常见方法是将足迹函数映射到相机空间中的足迹多边形中。在下一步中，足迹多边形被投影到屏幕空间并进行栅格化，形成所谓的足迹图像。然而，正如中所述，这需要大量的计算量。相比之下，我们的框架通过将体积映射到射线空间来高效地执行透视投影，这只需要计算雅可比矩阵和两次 $3 \times 3$​ 矩阵乘法。对于球形重建核，这些矩阵运算可以进一步优化，如第7.1节所示。</p>
<p><img alt="projection simplify" loading="lazy" src="http://localhost:1313/WangJV-Blog-Pages/2024/06/ewa-splatting/images/image-20240601150629840.png"></p>
<h4 id="623-带宽限制">6.2.3. 带宽限制<a hidden class="anchor" aria-hidden="true" href="#623-带宽限制">#</a></h4>
<p>我们根据公式(11)对公式(30)中的高斯重建核进行积分，得到高斯足迹函数 $q_k$:</p>
$$
q_k(\mathbf{x}) = \int_{\mathbb{R}} \frac{1}{|W^{-1}J_k^T|} G V_{0k}(\mathbf{x} - \mathbf{x}_k, x_2 - x_{k2}) \, dx_2 = \frac{1}{|W^{-1}J_k^T|} G \hat{V}V_{0k}(\mathbf{x} - \mathbf{x}_k)
$$<p>其中，足迹函数的 $2 \times 2$ 方差矩阵 $\hat{V}V_{0k}$ 是从 $V_{0k}$ 中去掉最后一行和最后一列得到的，如公式(24)所示。最后，我们选择一个高斯低通滤波器 $h(\mathbf{x}) = G V_h(\mathbf{x})$，其中 $V_h$ 是通常为单位矩阵的 $2 \times 2$ 方差矩阵。利用公式(22)，我们计算公式(14)中的卷积，得到EWA体积重采样滤波器，或EWA体积喷射：</p>
$$
\hat{\mathbf{k}}(\mathbf{x}) = (p_k * h)(\mathbf{x}) = c_k \frac{1}{|W^{-1}J_k^T|} (G \hat{V}V_{0k} * G V_h)(\mathbf{x} - \mathbf{x}_k) = c_k \frac{1}{|W^{-1}J_k^T|} (G \hat{V}V_{0k} + V_h)(\mathbf{x} - \mathbf{x}_k)
$$<h3 id="63-ewa-表面重采样滤波器">6.3. EWA 表面重采样滤波器<a hidden class="anchor" aria-hidden="true" href="#63-ewa-表面重采样滤波器">#</a></h3>
<p>在本节中，我们首先描述了如何构建所需的局部参数化，以定义表面属性函数 $f_c(u)$（第5.1节）。然后，我们推导了一个映射 $x^{1/4}m(u)$，涉及参数化、视图变换和透视投影，将属性函数从源空间转换到屏幕空间，类似于第6.2节。</p>
<h4 id="621-表面参数化">6.2.1. 表面参数化<a hidden class="anchor" aria-hidden="true" href="#621-表面参数化">#</a></h4>
<p>在每个点$P_k$处，局部表面参数化$k$由表面切平面上的两个正交单位向量$e_{0k}$和$e_{1k}$定义。切平面由存储在每个点$P_k$上的表面法线$n_k$给出。因此，参数域中的每个点$u=(u_0, u_1)$对应于对象空间中表面上的一个点$\hat{u}=(\hat{u}_0, \hat{u}_1, \hat{u}_2)$</p>
$$
\hat{u}=k(u) = P_k + Sku
$$<p>其中$S_k$是由列向量$e_{0k}$和$e_{1k}$组成的$3 \times 2$矩阵。我们用$r_k(u)=GV_k(u)$表示参数域中的高斯表面重建核。方差矩阵$V_k$必须适当选择以匹配点$P_k$周围的局部点密度。限制自己于径向对称核，$V_k$是一个由因子$\sigma^2$缩放的$2 \times 2$单位矩阵，即$V_k = \sigma^2I$。缩放因子$\sigma$取决于$P_k$及其最近邻点之间的距离，例如，我们选择$\sigma$为到六个最近邻点的平均距离。围绕$P_k$的点分布的更复杂的分析可用于找到适合一般椭圆核的合适方差矩阵。</p>
<h4 id="622-视角变换">6.2.2. 视角变换<a hidden class="anchor" aria-hidden="true" href="#622-视角变换">#</a></h4>
<p>视角变幻将世界坐标系 $u$ 变换到相机坐标系 $t$，有：</p>
$$
\mathbf{t}=\varphi(\mathbf{\hat{u}})=\mathbf{W}\mathbf{\hat{u}}+\mathbf{d}
$$<h4 id="623-射影变换">6.2.3. 射影变换<a hidden class="anchor" aria-hidden="true" href="#623-射影变换">#</a></h4>
<p>三维相机空间中的表面点$t$投影到二维屏幕空间$x$，通过除以深度坐标$t^2$来实现。因此，我们使用与体积相同的映射$\varphi$（见（26）），只是我们不需要第三个坐标$x_2$：</p>
$$
\begin{pmatrix} x_0 \\ x_1 \end{pmatrix} = \varphi(t) = \frac{1}{t_2}\begin{pmatrix} t_0 \\ t_1 \end{pmatrix}。
$$<p>我们使用与（28）中相同的局部仿射逼近$\varphi_k$</p>
<p>请注意，这里的Jacobian矩阵$J_k$是一个$2 \times 3$矩阵：</p>
$$
J_k = \begin{pmatrix} \frac{\partial \varphi}{\partial t} (t_k) & \frac{1}{t_k;2} 0 & \frac{\partial \varphi}{\partial t} (t_k) \frac{t_k;0}{t_k;2} 0 & \frac{1}{t_k;2} \\ \frac{\partial t_k;1}{t_k;2} \end{pmatrix}
$$<p>将$k$、$'$和$\varphi_k$连接起来，我们得到了从源空间到屏幕空间的局部仿射逼近$m_k$：</p>
$$
x = m_k(u) = \varphi_k('(k(u))) + J_kWS_ku + x_k
$$<p>将$u = m_k^{-1}(x)$代入，并应用（21），我们得到了屏幕空间中的高斯表面重建核：</p>
$$
r_{0k}(x) = GV_k(m_k^{-1}(x) - u_k) = \frac{1}{|\mathbf{M}_k^{-1}|}GV_{0k}(x - m_k(u_k))
$$<p>其中:</p>
$$
\begin{aligned}
V_{0k} &= \mathbf{M}_kV_k\mathbf{M}_k^T\\
\mathbf{M}_k &= J_kWS_k
\end{aligned}
$$<h4 id="624-频带限制">6.2.4. 频带限制<a hidden class="anchor" aria-hidden="true" href="#624-频带限制">#</a></h4>
<p>使用 Gaussian 低通滤波器 $h=\mathcal G_{V^h}$，EWA 表面重采样滤波器可以写出：</p>
$$
\begin{aligned}
\rho_{k}(\mathbf{x})& =c_k(r_k^{\prime}\otimes h)(\mathbf{x})  \\
&=c_k\frac1{|\mathbf{M}_k^{-1}|}\mathcal{G}_{\mathbf{V}_k^{\prime}+\mathbf{V}^h}(\mathbf{x}-\mathbf{m}_k(\mathbf{u}_k)).
\end{aligned}
$$<p>上式中，重采样滤波器是一个像素空间上的函数。由于仿射变换是可逆的，有：</p>
$$
\mathbf{x}-\mathbf{m}_k(\mathbf{u}_k)=\mathbf{J}_k\mathbf{J}_k^{-1}(\mathbf{x}-\mathbf{m}_k(\mathbf{u}_k))=\mathbf{J}_k(\mathbf{m}_k^{-1}(\mathbf{x})-\mathbf{u}_k),
$$<p>带入到之前推倒的重建核中，有：</p>
$$
\rho_k(\mathbf{x})=c_k\mathcal{G}_{\mathbf{V}_k+\mathbf{M}_k^{-1}\mathbf{V}^h\mathbf{M}_k^{-1^T}}(\mathbf{u}-\mathbf{u}_k).
$$<p>这是著名的源空间EWA方法[9]，针对不规则采样位置进行了扩展，数学上等价于我们的屏幕空间表述。然而，(36)涉及将屏幕上的点$x$反向映射到物体表面，这对于交互式渲染来说是不切实际的。这相当于对点云进行光线跟踪以找到表面交点。此外，位置$u_k$是不规则的，使得在物体空间中评估重采样核变得繁琐。另一方面，(35)可以有效地用于基于点的对象，如第7.2节所述。</p>
<p><img alt="Geometry" loading="lazy" src="http://localhost:1313/WangJV-Blog-Pages/2024/06/ewa-splatting/images/image-20240601153717201.png"></p>


  </div>

  <footer class="post-footer">
    <ul class="post-tags">
      <li><a href="http://localhost:1313/WangJV-Blog-Pages/tags/%E4%BD%93%E6%B8%B2%E6%9F%93/">体渲染</a></li>
      <li><a href="http://localhost:1313/WangJV-Blog-Pages/tags/%E9%93%BA%E6%91%8A%E7%AE%97%E6%B3%95/">铺摊算法</a></li>
      <li><a href="http://localhost:1313/WangJV-Blog-Pages/tags/ewa/">EWA</a></li>
      <li><a href="http://localhost:1313/WangJV-Blog-Pages/tags/%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%9B%BE%E5%BD%A2%E5%AD%A6/">计算机图形学</a></li>
    </ul>
<nav class="paginav">
  <a class="prev" href="http://localhost:1313/WangJV-Blog-Pages/2024/07/hierarchical-gaussian-splatting/">
    <span class="title">« Prev</span>
    <br>
    <span>Hierarchical Gaussian Splatting</span>
  </a>
  <a class="next" href="http://localhost:1313/WangJV-Blog-Pages/2024/05/mathematics-in-3dgs-2/">
    <span class="title">Next »</span>
    <br>
    <span>Mathematics In 3DGS 2</span>
  </a>
</nav>

  </footer>
</article>
    </main>
    
<footer class="footer">
        <span>&copy; 2025 <a href="http://localhost:1313/WangJV-Blog-Pages/">WangJV Blog</a></span> · 

    <span>
        Powered by
        <a href="https://gohugo.io/" rel="noopener noreferrer" target="_blank">Hugo</a> &
        <a href="https://github.com/adityatelange/hugo-PaperMod/" rel="noopener" target="_blank">PaperMod</a>
    </span>
</footer>
<a href="#top" aria-label="go to top" title="Go to Top (Alt + G)" class="top-link" id="top-link" accesskey="g">
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 6" fill="currentColor">
        <path d="M12 6H0l6-6z" />
    </svg>
</a>

<script>
    let menu = document.getElementById('menu')
    if (menu) {
        menu.scrollLeft = localStorage.getItem("menu-scroll-position");
        menu.onscroll = function () {
            localStorage.setItem("menu-scroll-position", menu.scrollLeft);
        }
    }

    document.querySelectorAll('a[href^="#"]').forEach(anchor => {
        anchor.addEventListener("click", function (e) {
            e.preventDefault();
            var id = this.getAttribute("href").substr(1);
            if (!window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
                document.querySelector(`[id='${decodeURIComponent(id)}']`).scrollIntoView({
                    behavior: "smooth"
                });
            } else {
                document.querySelector(`[id='${decodeURIComponent(id)}']`).scrollIntoView();
            }
            if (id === "top") {
                history.replaceState(null, null, " ");
            } else {
                history.pushState(null, null, `#${id}`);
            }
        });
    });

</script>
<script>
    var mybutton = document.getElementById("top-link");
    window.onscroll = function () {
        if (document.body.scrollTop > 800 || document.documentElement.scrollTop > 800) {
            mybutton.style.visibility = "visible";
            mybutton.style.opacity = "1";
        } else {
            mybutton.style.visibility = "hidden";
            mybutton.style.opacity = "0";
        }
    };

</script>
<script>
    document.getElementById("theme-toggle").addEventListener("click", () => {
        if (document.body.className.includes("dark")) {
            document.body.classList.remove('dark');
            localStorage.setItem("pref-theme", 'light');
        } else {
            document.body.classList.add('dark');
            localStorage.setItem("pref-theme", 'dark');
        }
    })

</script>
<script>
    document.querySelectorAll('pre > code').forEach((codeblock) => {
        const container = codeblock.parentNode.parentNode;

        const copybutton = document.createElement('button');
        copybutton.classList.add('copy-code');
        copybutton.innerHTML = 'copy';

        function copyingDone() {
            copybutton.innerHTML = 'copied!';
            setTimeout(() => {
                copybutton.innerHTML = 'copy';
            }, 2000);
        }

        copybutton.addEventListener('click', (cb) => {
            if ('clipboard' in navigator) {
                navigator.clipboard.writeText(codeblock.textContent);
                copyingDone();
                return;
            }

            const range = document.createRange();
            range.selectNodeContents(codeblock);
            const selection = window.getSelection();
            selection.removeAllRanges();
            selection.addRange(range);
            try {
                document.execCommand('copy');
                copyingDone();
            } catch (e) { };
            selection.removeRange(range);
        });

        if (container.classList.contains("highlight")) {
            container.appendChild(copybutton);
        } else if (container.parentNode.firstChild == container) {
            
        } else if (codeblock.parentNode.parentNode.parentNode.parentNode.parentNode.nodeName == "TABLE") {
            
            codeblock.parentNode.parentNode.parentNode.parentNode.parentNode.appendChild(copybutton);
        } else {
            
            codeblock.parentNode.appendChild(copybutton);
        }
    });
</script>
</body>

</html>
